"""
虚拟机管理模块
"""
import requests
import base64
import copy
import datetime
import json
import os
import random
import time
from typing import List, Callable
from collections import OrderedDict

import IPy
import traceback
import openstack
from celery import group
from tasks.tasks import startAllVm
from flask import current_app, request
from flask_restx import abort, reqparse
from gnocchiclient.v1 import client as gnocchi
from keystoneauth1 import session
from keystoneauth1.identity import v3
from openstack import exceptions
from openstack.exceptions import ConflictException, ResourceTimeout, ResourceNotFound
from werkzeug.exceptions import HTTPException

from app.api.v1.organizer.rangeEnv import RangeBase
from app.api.v1.webhook.webhook import WebHookManager
from tools.basic_tools import Tools
from app.api.v1.organizer.taas import TaasService
from app.common.range_manager import LinkType as RangeLinkType
from app.common.range_manager import PrivateRangeException, RangeManager
from app.models import (
    CtfQuestion,
    CtfRedBlue,
    Image,
    ContainerFormat,
    RangeVm,
    courseModel,
    rangeModel,
    switchModel,
    taasModel,
    RangeLinkModule,
    VmType
)
from app.models.rangeModel import SecGroup
from app.models.userModel import User
from tools import db
from tools import redis_conn as redis_store
from utils.crypto import decrypt_pw
from utils.utils import (
    check_ip,
    check_port,
    counter_time,
    init_openstack,
    is_Chinese,
    list_dict_duplicate_removal,
    update_rule_body,
    time_out,
    ThreadTimeOutException
)
from utils.vthread import vthread
from tools.openstack_ext import ops
from typing import Union
from zunclient.v1.containers import Container
from zunclient.common.apiclient import exceptions as zun_api_exceptions
from app.common.zstack_manager import ZStackManager
from app.common.transaction_manager import create_transaction


class VmBase(RangeBase):
    def __init__(self, api=None, *args, **kwargs):
        super().__init__(api=api, *args, **kwargs)
        """
        根据客户定制配置虚拟化参数
        """

        # self.conn = init_openstack(self.user.project_id, current_app.config)
        self.conn = ops.conn
        """
        openstackAuthConfig = decrypt_pw(
            current_app.config['OPENSTACK']['auth'])
        openstackAuthConfig['project_id'] = self.user.project_id

        try:
            self.conn = openstack.connect(
                auth=openstackAuthConfig,
                auth_type=current_app.config['OPENSTACK']['auth_type'])
        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="无法连接到云计算服务")
        """
        self.redis = redis_store

    def wait_for_server_status(self, op_vm_uuid, status="ACTIVE"):
        # 再次获取虚拟机
        server = self.conn.compute.find_server(op_vm_uuid)
        # 等候状态
        self.conn.compute.wait_for_server(server, status)
        return

    @time_out(30)
    def wait_for_container_status(self, container_ident: Union[str, Container], status="Created"):
        # 获取容器
        container_id = container_ident if isinstance(container_ident, str) else container_ident.uuid
        while True:
            container: Container = ops.zun_operate.get_container_details(container_id)
            if container.status == status:
                return container

    # 判断靶场 下面是否存在某虚机
    def getRangeVm(self, range_id, vm_uuid=None):
        if range_id:
            rangVmList = rangeModel.RangeVm.query.filter_by(
                range_id=range_id, vm_uuid=vm_uuid, user_id=self.user.id
            ).first()

        if not range_id:
            rangVmList = rangeModel.RangeVm.query.filter_by(user_id=self.user.id).all()

        return rangVmList

    # 获取控制台地址
    def get_console_url(self, id):
        try:
            url = self.conn.compute.get_server_console_url(id, console_type="novnc")
        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="无法连接到云计算服务")
        return url

    def getRangeVmByUUID(self, range_uuid, vm_uuid=None, is_red_blue=False):
        rangeInfo = self.getMyRangeByUUID(range_uuid=range_uuid)

        _filter = {"range_uuid": range_uuid}
        if not is_red_blue and not self.user.is_superuser:
            _filter["user_id"] = self.user.id

        if vm_uuid:
            _filter["vm_uuid"] = vm_uuid
            rangeVmList = rangeModel.RangeVm.query.filter_by(**_filter).first()
        else:
            rangeVmList = rangeModel.RangeVm.query.filter_by(**_filter).all()
        # 返回 靶场及虚机信息
        return rangeVmList, rangeInfo

    # def check_range_new(self, range_id):
    #     rangInfo = self.getMyRange(range_id=range_id)
    #     if not rangInfo:
    #         abort(400, message="参数有误")
    #     return rangInfo

    # 获取虚机方法
    # def get_vm(self, range_id, vm_uuid):
    #     # 1、先检测靶场是否是当前账号
    #     # 2、再验证靶场和虚机的关联关系
    #     # self.check_range(range_id)
    #     vmInfo = self.getRangeVm(range_id=range_id, vm_uuid=vm_uuid)
    #     return vmInfo
    # 同一个靶场只能有一个外网地址
    def checkRangeExternal(self, range_uuid):
        return (
            rangeModel.RangeVm.query.filter_by(
                range_uuid=range_uuid, is_external=1, user_id=self.user.id
            )
            .filter(rangeModel.RangeVm.type.not_in(["NIDS", "HIDS"]))
            .first()
        )

    def createNetwork(self, range_uuid):
        if not range_uuid:
            abort(400, message="参数错误")
        # 检测参数是否正确
        rangLinkModuleInfo = self.getMyRangeByUUID(range_uuid=range_uuid)

        if not rangLinkModuleInfo:
            abort(400, message="参数错误")

        netName = f"net-{str(rangLinkModuleInfo.id)}"

        try:
            example_network = self.conn.network.create_network(name=netName)
        except Exception as e:
            rangeModel.db.session().rollback()
            current_app.logger.exception(e)
            abort(500, message="获取失败")

        return example_network.id


class VmManage(VmBase):
    """获取虚拟机信息R/W"""

    def get(self, range_id):
        # 1、判断靶场是否存在
        self.check_range(range_id)
        if id is None:
            # 虚拟机列表
            vm_list = []
            # 获取虚拟机列表
            for server in self.conn.compute.servers():
                url = self.get_console_url(server.id)  # noqa
                vm_list.append({"id": server.id, "name": server.name})
            return {"code": 200, "info": vm_list}
        else:
            # 获取具体虚拟机信息
            info = self.conn.get_server(id)
            return {"code": 200, "data": info}

    def create_kvm_vm(self, args: dict, range_info: RangeLinkModule) -> RangeVm:
        """创建kvm虚拟机"""
        # 根据配置文件取得镜像对应模板大小
        flavor = Image.get_flavor_id(args["image"])

        # 当未填写虚机名称时候，系统默认
        op_vm_name = args["type"] + "-" + str(time.time())
        vmname = args["vm_name"] or op_vm_name
        vmNum = rangeModel.RangeVm.query.filter_by(
            range_uuid=range_info.range_uuid, user_id=self.user.id
        ).count()

        is_external = 1 if args["type"] in ["HIDS", "NIDS"] else args["external"]
        obj = rangeModel.RangeVm(
            vm_name=vmname,
            range_uuid=range_info.range_uuid,
            image=args["image"],
            flavor=flavor,
            is_external=is_external,
            status=1,
            type=args["type"],
            user_id=self.user.id,
        )
        db.session.add(obj)
        db.session.flush()

        rangeModel.Range.query.filter_by(
            id=range_info.range_id, user_id=self.user.id
        ).update({"vmNum": vmNum + 1})
        db.session.commit()

        return obj

    def create_container_vm(self, args: dict, image: Image, range_info: RangeLinkModule) -> RangeVm:
        """创建容器虚拟机"""

        op_vm_name = f"{args['type']}-docker-{time.time()}"

        vm_name = args["vm_name"] if args["vm_name"] else op_vm_name
        vm_numm = rangeModel.RangeVm.query.filter_by(
            range_uuid=range_info.range_uuid, user_id=self.user.id
        ).count()

        obj = rangeModel.RangeVm(
            vm_name=vm_name,
            range_uuid=range_info.range_uuid,
            image=args["image"],
            is_external=args["external"],
            status=1,
            type=image.image_type,
            user_id=self.user.id,
        )
        db.session.add(obj)
        db.session.flush()

        rangeModel.Range.query.filter_by(
            id=range_info.range_id, user_id=self.user.id
        ).update({"vmNum": vm_numm + 1})
        db.session.commit()

        return obj

    # 创建虚机
    def post(self):
        """
        1、提供镜像源, flavor
        2、flavor实例模板
        3、选择网络
        """

        # 超配额限制
        myVmCount = rangeModel.RangeVm.query.filter_by(user_id=self.user.id).count()
        if myVmCount and (
            myVmCount
            > current_app.config["USER_TO_OPENSTACK"][self.user.project_id][
                "quotas"
            ]["instances"]
        ):
            return {"code": 200, "message": "超配额"}

        parser = reqparse.RequestParser()
        parser.add_argument("type", help="虚拟设备类型不能为空", required=True)
        parser.add_argument("external", help="设置出口主机", type=int, default=0)
        parser.add_argument("image", help="镜像不能为空", required=True)
        parser.add_argument("range_uuid", type=str, help="UUID不能为空", required=True)
        parser.add_argument("vm_name", type=str)
        args = parser.parse_args()

        if not all([args["range_uuid"], args["image"]]):
            abort(400, message="参数错误")

        # 检测参数是否正确
        rangLinkModuleInfo = self.getMyRangeByUUID(range_uuid=args["range_uuid"])
        if not rangLinkModuleInfo:
            abort(400, message="参数有误")

        is_external = args["external"]
        if is_external:
            external = self.checkRangeExternal(range_uuid=args["range_uuid"])
            if external:
                abort(400, message="请注意！同一个靶场只能有一个外网出口地址！")

        if args["type"] not in VmType:
            abort(400, message="参数错误")

        image = Image.query.filter(Image.image_id == args["image"]).first()
        if not image:
            abort(400, message="镜像不存在")
        try:
            if image.container_format == ContainerFormat.docker:
                obj = self.create_container_vm(args, image, rangLinkModuleInfo)
            else:
                obj = self.create_kvm_vm(args, rangLinkModuleInfo)
        except Exception as e:
            db.session.rollback()
            current_app.logger.exception(e)
            abort(500, message="系统错误")
        else:
            return {"code": 200, "data": {"vm_uuid": obj.vm_uuid}}

    def put(self):
        parser = reqparse.RequestParser()
        parser.add_argument("vm_uuid", help="vm_uuid不能为空", required=True)
        parser.add_argument("range_uuid", help=" 靶场range_uuid有误", required=True)
        parser.add_argument("vm_name", type=str, help="虚拟机名称有误", required=True)
        args = parser.parse_args()

        # 判断是否有此虚拟机
        range_manager = RangeManager(range_uuid=args["range_uuid"])
        vmInfo = range_manager.get_vm_from_this(vm_uuid=args["vm_uuid"])

        try:
            rangeModel.RangeVm.query.filter_by(
                id=vmInfo.id, user_id=self.user.id
            ).update({"vm_name": args["vm_name"]})
            rangeModel.db.session.commit()

        except Exception as e:
            rangeModel.db.session().rollback()
            current_app.logger.exception(e)
            abort(500, message="无法连接到虚拟机")

        return {"code": 200, "info": "修改成功"}


# 一键开启场景虚机/容器
class AllVmOperate(VmBase):
    
    @property
    def load_cloud_object(self):
        return WebHookManager()

    def check_range(self, args, vmList):
        """
        校验当前靶场
        """
        for vmInfo in vmList:
            row_ports = switchModel.Ports.query.filter_by(subnet_id=vmInfo.subnet_id, range_uuid=args["range_uuid"],
                                                          device_id=None).all()

            if row_ports:
                nics = []
                if row_ports[0].port_id and row_ports[0].port_id.strip():
                    sub_port = switchModel.Ports.query.filter_by(
                        port_id=row_ports[0].port_id
                    ).first()
                    nics.append({"net-id": sub_port.network_id, "port-id": row_ports[0].port_id})
                if vmInfo.type == 'firewall':

                    ports = switchModel.Ports.query.filter_by(
                        device_id=vmInfo.vm_uuid
                    ).all()
                    # 防火墙接口数量小于2时 限制开机
                    if len(ports) <= 1:
                        abort(400, message="防火墙绑定网卡数量为1时无法开机")
                    nics.extend(
                        {"net-id": port.network_id, "port-id": port.port_id}
                        for port in ports
                    )
                if not nics:
                    abort(400, message="该非vpn虚拟机没有给定端口参数")

    def powerOn(self, range_model, range, args):
        action_params = OrderedDict()
        port_info = switchModel.Ports.query.filter_by(range_uuid=range_model.range_uuid).all()
        port_vm_dict = {f"{port.device_id}-{port.subnet_id}": {"port_id": port.port_id, "network_id": port.network_id} for port in port_info}

        # 二层网络
        if not range_model.network_id:
            network_id = Tools.get_uuid4_no_line()
            action_params["create_l2_vxlan_network"] = {
                "pool_uuid": range_model.vxlan_pool_id,
                "name": range.name,
                "zone_uuid": current_app.config["ZSTACK_CLOUD"]["zone_uuid"],
                "resource_uuid": network_id,
                "faild_message": {"operate": "open_failed", "message": "基础网络创建失败"}
            }
        else:
            network_id = range_model.network_id
        # 路由器
        route_switch = {}
        routes = switchModel.Routes.query.filter_by(range_uuid=range_model.range_uuid).all()
        for  route  in routes:
            if route.state == 0:
                action_params[f"create_vpc_router-{route.op_route}"] = {
                    "name": f"{route.op_route[:5]}-路由器",
                    "router_offering_uuid": current_app.config["ZSTACK_CLOUD"]["router_uuid"],
                    "resource_uuid": route.op_route,
                    "faild_message": {"operate": "open_failed", "message": "路由器创建失败"}
                }
            for switch_id in route.binding_switchs.split(","):
                route_switch[int(switch_id)] = route.op_route
        range_vm = RangeVm.query.filter_by(range_uuid=range_model.range_uuid, state=0).all()
        vm_eip_switch = {vm.subnet_id: 1 for vm in range_vm if vm.is_external}
        # 三层网络
        switch_dict = {}
        switchs = switchModel.Switchs.query.filter_by(range_uuid=range_model.range_uuid).all()
        for switch in switchs:
            switch_id = switch.id
            op_route = route_switch.get(switch_id)
            # eip = vm_eip_switch.get(switch.subnet_id)
            network_service = current_app.config["ZSTACK_CLOUD"]["network_service"]
            network_route_eip_service = []
            if op_route:
                network_route_eip_service.append("VRouterRoute")
            # if eip:
            #     network_route_eip_service.append("Eip")
            if network_route_eip_service:
                network_service[current_app.config["ZSTACK_CLOUD"]["network_route_eip_service"]] += network_route_eip_service
            switch_dict[switch_id] = switch.subnet_id
            if switch.network_id:
                continue
            action_params[f"create_l3_network-{switch.subnet_id}"] = {
                "l2_network_uuid": network_id,
                "name": switch.switch_name,
                "resource_uuid": switch.subnet_id,
                "faild_message": {"operate": "open_failed", "message": "交换机创建失败"}
            }
            action_params[f"attach_network_service_to_l3Network-{switch.subnet_id}"] = {
                    "l3_network_uuid": switch.subnet_id,
                    "network_service": network_service,
                    "faild_message": {"operate": "open_failed", "message": "交换机创建失败"}
            }
            
            action_params[f"create_l3_network_ip_ranges-{switch.subnet_id}"] = {
                    "l3_network_uuid": switch.subnet_id, 
                    "network_cidr": switch.cidr,
                    "faild_message": {"operate": "open_failed", "message": "交换机创建失败"}
            }
            action_params[f"add_dns_to_l3_network-{switch.subnet_id}"] = {
                "l3_network_id": switch.subnet_id,
                "dns": current_app.config["ZSTACK_CLOUD"]["dns"],
                "faild_message": {"operate": "open_failed", "message": "交换机创建失败"}
            }
        
        # 交换机与路由器连接
        for rs, op_route in route_switch.items():
            subnet_id = switch_dict[rs]
            if not port_vm_dict.get(f"{op_route}-{subnet_id}").get("network_id"):
                action_params[f"attach_l3network_route-{subnet_id}"] = {
                    "route_uuid": op_route,
                    "l3_network_uuid": subnet_id,
                    "faild_message": {"operate": "open_failed", "message": "交换机与路由器关联失败"}
                }
                action_params[f"update_vm_ip_database-{subnet_id}"] = {
                    "port_id": port_vm_dict.get(f"{op_route}-{subnet_id}").get("port_id"),
                    "device_type": "router",
                    "l3_network_uuid": subnet_id
                }
        # 入口
        nids = {}
        try:
            for vm in range_vm:
                op_vm_uuid = Tools.get_uuid4_no_line()
                ip = vm.v4_fixed_ip
                update_vm = {"op_vm_uuid": op_vm_uuid, "op_vm_name": vm.vm_name}
                l3_network_uuids = [vm.subnet_id]
                device_type = "vm"
                if vm.type == "NIDS":
                    l3_network_uuids.insert(0, current_app.config["ZSTACK_CLOUD"]["mirror_network_uuid"])
                    nids[vm.vm_uuid] = vm.subnet_id
                    device_type = "NIDS"
                action_params[f"create_vm-{vm.vm_uuid}"] = {
                    "name": vm.vm_name,
                    "instance_offering_uuid": vm.flavor,
                    "image_uuid": vm.image,
                    "dataDisk_offering_uuids": [], 
                    "resource_uuid": op_vm_uuid, 
                    "l3Network_uuids": l3_network_uuids,
                    "default_l3_network_uuid": l3_network_uuids[0],
                    "systemTags": [f"staticIp::{l3_network_uuids[-1]}::{ip}"] if ip else [],
                    "faild_message": {"operate": "open_failed", "message": "虚拟机创建失败"}
                }
                action_params[f"update_vm_ip_database-{vm.vm_uuid}"] = {
                    "port_id": port_vm_dict.get(f"{vm.vm_uuid}-{vm.subnet_id}").get("port_id"),
                    "device_type": device_type,
                    "op_vm_uuid": op_vm_uuid
                }   
                if vm.is_external:
                    vip_uuid, eip_uuid = Tools.get_uuid4_no_line(), Tools.get_uuid4_no_line()
                    public_network = current_app.config["ZSTACK_CLOUD"]["public_network"]
                    action_params[f"create_vip_action-{vm.vm_uuid}"] = {"name": vm.vm_name, "l3_network_uuid": public_network, "resource_uuid": vip_uuid}
                    action_params[f"create_eip_action-{vm.vm_uuid}"] = {"vip_uuid": vip_uuid, "resource_uuid": eip_uuid}
                    action_params[f"update_vm_external_ip_database-{vm.vm_uuid}"] = {"vm_uuid": vm.vm_uuid}
                    action_params[f"attach_eip_to_vm-{vm.vm_uuid}"] = {"eip_uuid": eip_uuid, "vm_uuid": vm.vm_uuid}
                # 更新数据库
                rangeModel.RangeVm.query.filter_by(id=vm.id).update(update_vm)
            db.session.commit()
        except Exception:
            db.session.rollback()
            print(traceback.print_exc())
        # nids
        for ni_key, ni_value in nids.items():
            src_end_points_vm = [
                port.device_id
                for port in port_info
                if port.subnet_id == ni_value
                and port.device_owner == "vm"
                and port.device_id != ni_key
            ]
            action_params[f"create_all_port_mirror_session-{ni_key}"] = {
                    "src_end_points_vm": src_end_points_vm, "nids_vm_uuid": ni_key,
                    "faild_message": {"operate": "open_failed", "message": "NIDS监控开启失败"}}
        if len(action_params) == 0:
            abort(400, message="无需要开启的设备")
        
        action_params["update_all_operate_database"] = {
            "network_id": network_id,
            "range_uuid": range_model.range_uuid,
            "state": 1,
            "range_id": range.id
        }
        create_transaction(action_params, range_model.range_uuid)
        #更新json
        update_topology_json = Tools.update_topology_json(range.topology_json, all_state="BUILD")
        try:
            rangeModel.Range.query.filter_by(id=range.id).update({"topology_json": json.dumps(update_topology_json, ensure_ascii=False)})
            db.session.commit()
        except Exception:
            db.session.rollback()
            print(traceback.print_exc())
        return {"code": 200, "data": "批量开机中"}
    
    def post(self):  # sourcery skip: avoid-builtin-shadow
        parser = reqparse.RequestParser()
        parser.add_argument(
            "action", choices=("powerOn",), help="操作码异常: {error_msg}", required=True
        )
        parser.add_argument("range_uuid", help=" 靶场参数有误", required=True)
        args = parser.parse_args()
        user_id = self.user.id
        # 查询该场景是否已经开启，TODO[现在暂时只支持系统模板能一键启动]
        range_manager = RangeManager(range_uuid=args["range_uuid"])
        range_model = range_manager.range_model
        range = range_manager.range_model.Range
        # 检查是否满足开启条件
        if not range_model.clone_range_uuid:
            abort(400, message="不满足开启条件")
        # if range_model.network_id:
        #     abort(400, message="已存在开启设备")
           
        option_func: Callable = getattr(self, args["action"])
        return option_func(range_model, range, args)


# 操作虚拟机
class VmOperate(VmBase):
    """进入拓扑页面，对虚拟机进行开/关机/删除/修改名称等操作"""

    def check_role(self):
        if self.user.role_id == User.RoleChoices.rb_pilot.value:
            abort(400, message="导调不允许修改拓扑")

    def get_cidr(self, ip_address):
        ip_list = ip_address.split(".")[:-1]
        return ".".join(ip_list) + "0/24"
    
    @property
    def load_cloud_object(self):
        return WebHookManager()
    

    @staticmethod
    def get_vpn_client_username_pswd():
        import random
        import string

        # 字符串
        data = string.ascii_letters + string.digits
        username, password = "", ""
        # 随机长度k (1<= k <=32)
        for _ in range(2):
            random_length = random.randint(4, 8)
            if username:
                password = "".join(random.sample(data, random_length))
            else:
                username = "".join(random.sample(data, random_length))

        return username, password

    def create_vpnserver(self, op_vm_name, vmInfo, cidr_list, range_uuid):

        usrname, password = self.get_vpn_client_username_pswd()
        str1 = (
                '#!/bin/sh\necho "%s %s" > /etc/openvpn/psw-file\nservice openvpn@server restart\n'
                % (usrname, password)
        )
        str2 = str1.encode()
        str3 = base64.b64encode(str2)
        str4 = str3.decode()
        networks = []

        if vmInfo.is_external:
            ex_port = self.conn.create_port(
                current_app.config["OPENSTACK"]["external_id"]
            )
            networks.append(
                {
                    "uuid": current_app.config["OPENSTACK"]["external_id"],
                    "port": ex_port["id"],
                }
            )
            vmport = switchModel.Ports(
                port_id=ex_port.id,
                range_uuid=range_uuid,
                ip=ex_port["fixed_ips"][0]["ip_address"],
                device_owner="ex-port",
                device_id=vmInfo.vm_uuid,
            )
            taasModel.db.session.add(vmport)
            taasModel.db.session.flush()

        cidr_list = cidr_list.split(",") if cidr_list else []
        for cidr in cidr_list:
            switch = switchModel.Switchs.query.filter_by(
                range_uuid=range_uuid, cidr=cidr
            ).first()
            port = self.conn.create_port(
                switch.network_id, fixed_ips=[{"subnet_id": switch.subnet_id}]
            )
            networks.append({"uuid": switch.network_id, "port": port.id})
            vmport = switchModel.Ports(
                port_id=port.id,
                range_uuid=range_uuid,
                ip=port["fixed_ips"][0]["ip_address"],
                device_owner="vpn-server",
                device_id=vmInfo.vm_uuid,
                subnet_id=switch.subnet_id,
            )
            taasModel.db.session.add(vmport)
            taasModel.db.session.flush()

        dict1 = {
            "name": op_vm_name,
            "imageRef": vmInfo.image,
            "flavorRef": vmInfo.flavor,
            "networks": networks,
            "OS-EXT-SRV-ATTR:user_data": str4,
            "config_drive": True,
        }
        info = self.conn.compute.create_server(**dict1)
        if vmInfo.is_external and info:
            rangeModel.RangeVm.query.filter_by(
                id=vmInfo.id, user_id=self.user.id
            ).update(
                {
                    "external_ip": ex_port.fixed_ips[0]["ip_address"],
                    "op_vm_uuid": info.id,
                }
            )

        return info, usrname, password

    def power_on_container(self, args, vmInfo):
        """开启容器"""
        if vmInfo.op_vm_uuid:
            try:
                container: Container = ops.zun_operate.get_container_details(vmInfo.op_vm_uuid)
                if container.status == "Running":
                    return {"code": 200, "info": "开机正常"}
                elif container.status == "Creating":
                    self.wait_for_container_status(vmInfo.op_vm_uuid, status="Created")
                    ops.zun_operate.start_container(vmInfo.op_vm_uuid)
                    return {"code": 200, "info": "开机中请稍后"}
                elif container.status == "Restarting":
                    return {"code": 200, "info": "开机中请稍后"}
                else:
                    ops.zun_operate.start_container(vmInfo.op_vm_uuid)
                    self.wait_for_container_status(vmInfo.op_vm_uuid, status="Running")
                    return {"code": 200, "info": "开机正常"}
            except zun_api_exceptions.Conflict as e:
                current_app.logger.exception(e)
                abort(400, message=f"开启容器失败, {e.message}")
            except ThreadTimeOutException:
                abort(400, message="开机超时，请重试")

        vmInfo.op_vm_name = vmInfo.op_vm_name or f"{vmInfo.type}-docker-{time.time()}"
        device_owner = VmType.container.value
        nics = []

        if args.get('port_id') and args["port_id"].strip():
            sub_port = switchModel.Ports.query.filter_by(
                port_id=args["port_id"]
            ).first()
            nics.append({"net-id": sub_port.network_id, "port-id": args["port_id"]})
        elif vmInfo.v4_fixed_ip:
            switch = switchModel.Switchs.query.filter_by(
                subnet_id=vmInfo.subnet_id
            ).first()
            port = self.conn.create_port(
                switch.network_id,
                fixed_ips=[
                    {
                        "ip_address": vmInfo.v4_fixed_ip,
                        "subnet_id": switch.subnet_id,
                    }
                ],
            )
            ports = switchModel.Ports(
                port_id=port.id,
                range_uuid=args["range_uuid"],
                ip=port.fixed_ips[0]["ip_address"],
                network_id=switch.network_id,
                subnet_id=switch.subnet_id,
            )
            db.session.add(ports)
            db.session.flush()
            nics.append({"net-id": switch.network_id, "port-id": port.port_id})

        if not nics:
            abort(400, message="该非vpn虚拟机没有给定端口参数")

        if vmInfo.is_external:
            ex_port = self.conn.create_port(
                current_app.config["OPENSTACK"]["external_id"]
            )
            ex_port_id = ex_port["id"]
            nics.append(
                {
                    "net-id": current_app.config["OPENSTACK"]["external_id"],
                    "port-id": ex_port_id,
                }
            )

        # 创建容器
        image = Image.query.filter(Image.image_id == vmInfo.image).first()
        container_tags = image.tags
        # FIXME: 非管理员账户似乎有问题，无法创建及指定一些东西
        environment = container_tags.get("environment", None)
        labels = container_tags.get("labels", None)
        container = ops.zun_operate.create_container(
            image=image.image_full_name, name=vmInfo.op_vm_name,
            cpu=container_tags.get("cpu", 0.2), memory=container_tags.get("memory", 500),
            tty=True, interactive=True,
            environment=environment or {
                "foo": "bar"
            },
            labels=labels or {
                "app": "hello"
            },
            image_driver="glance",
            availability_zone="nova",
            restart_policy={
                "Name": "always",
                "MaximumRetryCount": 0
            },
            nets=[{"port": item["port-id"]} for item in nics],
            host="compute-UniServer-R4900-G3-1",
        )

        if not container:
            return {"code": 400, "info": "开机失败"}
        rangeModel.RangeVm.query.filter_by(
            id=vmInfo.id, user_id=self.user.id
        ).update({"op_vm_uuid": container.uuid})

        for item in nics:
            switchModel.Ports.query.filter_by(port_id=item["port-id"]).update(
                {"device_owner": device_owner, "device_id": vmInfo.vm_uuid}
            )
        if vmInfo.is_external:
            rangeModel.RangeVm.query.filter_by(
                id=vmInfo.id, user_id=self.user.id
            ).update({"external_ip": ex_port.fixed_ips[0]["ip_address"]})
        db.session.add(vmInfo)
        db.session.commit()

        try:
            self.wait_for_container_status(container.uuid, status="Created")
            ops.zun_operate.start_container(container.uuid)
        except ThreadTimeOutException:
            return {"code": 200, "info": "开机中"}
        else:
            return {"code": 200, "info": "开机正常"}

    def _check_switch_to_attach_detach(self, subnet_id, rangInfo):
        """检测交换机"""
        switch_info = self.load_cloud_object.get_l3_network(subnet_id)
        for switch in  switch_info:
            for service in  switch["networkServices"]:
                if service["networkServiceType"] == "Eip":
                    return  True
        # 不存在服务则添加服务
        action_param = {
            "attach_network_service_to_l3Network": {
                "l3_network_uuid": subnet_id,
                "network_service" : {current_app.config["ZSTACK_CLOUD"]["network_route_eip_service"]: ["Eip"]}
            }
        }
        create_transaction(action_param, rangInfo.range_uuid)
        return True
    
    def power_on_vm(self, args, vmInfo, rangInfo):
        if vmInfo.op_vm_uuid:
            # 开机
            action_params = {
                "start_vm": {
                    "vm_uuid": vmInfo.op_vm_uuid,
                    "state": 1
                }
            }
            create_transaction(action_params, vmInfo.range_uuid)
            return {"code": 200, "info": "开机中,请稍等"}
        # 通过port_id查找subnet_id
        if not switchModel.Ports.query.filter_by(port_id=args["port_id"]).first():
            abort(400, message="未找到关联交换机信息")
        
        # 虚拟机名称
        vmInfo.op_vm_name = vmInfo.op_vm_name or f"{vmInfo.type}-{time.time()}"
        subnet_id = vmInfo.subnet_id
        l3_network_uuids = [subnet_id]
        # 加载虚拟机创建信息
        op_vm_uuid = Tools.get_uuid4_no_line()
        ip = args["ip"]
        src_end_points = []
        dst_end_points = ""
        action_params = OrderedDict()
        action_params["create_vm"] = {}
        try:
            update_vm = {"op_vm_uuid": op_vm_uuid, "op_vm_name": vmInfo.op_vm_name}
            if vmInfo.type == "NIDS":
                # 添加虚拟网卡， 一个做默认网卡， 一个用作镜像网卡, 查找相关联的vm
                switch_port_list = switchModel.Ports.query.filter_by(subnet_id=subnet_id, device_owner="vm").all()
                # 确认绑定的服务是否开机
                for switch in switch_port_list:
                    device_id =  switch.device_id
                    range_vm = RangeVm.query.filter_by(vm_uuid=device_id).first()
                    if range_vm  and  range_vm.op_vm_uuid:
                        src_end_points.append(switch.port_id)
                # 还需要绑定会话, 公网网卡做默认网卡
                l3_network_uuids.insert(0, current_app.config["ZSTACK_CLOUD"]["mirror_network_uuid"])
            else:
                dst_end_points = self._inspection_nids_service(vmInfo)
                # 创建弹性ip
                if vmInfo.is_external == 1:
                    # 确认是否存在路由器
                    port_info = switchModel.Ports.query.filter_by(subnet_id=subnet_id, device_owner="router_gw").first()
                    if not port_info:
                        abort(400, message="交换机未关联路由器， 不能给服务器创建入口")
                    # 确认路由是否启动
                    if not switchModel.Routes.query.filter_by(op_route=port_info.device_id, state=1).first():
                        abort(400, message="提示请先开启路由器")
                    # 检测交换机否有eip服务
                    self._check_switch_to_attach_detach(subnet_id, rangInfo)
                    vip_uuid, eip_uuid = Tools.get_uuid4_no_line(), Tools.get_uuid4_no_line()
                    public_network = current_app.config["ZSTACK_CLOUD"]["public_network"]
                    vip_params = OrderedDict()
                    vip_params["create_vip_action"] = {"name": vmInfo.op_vm_name, "l3_network_uuid": public_network, "resource_uuid": vip_uuid}
                    vip_params["create_eip_action"] = {"vip_uuid": vip_uuid, "resource_uuid": eip_uuid}
                    vip_params["update_vm_external_ip_database"] = {"vm_uuid": vmInfo.vm_uuid}
                    create_transaction(vip_params, rangInfo.range_uuid)
            if ip:
                switchModel.Ports.query.filter_by(port_id=args.port_id).update({"ip": ip})
                update_vm["v4_fixed_ip"] = ip
            elif vmInfo.v4_fixed_ip:
                ip = vmInfo.v4_fixed_ip
            rangeModel.RangeVm.query.filter_by(
                id=vmInfo.id
            ).update(update_vm)
            db.session.commit()
            action_params["create_vm"] = {
                "name": vmInfo.op_vm_name,
                "instance_offering_uuid": vmInfo.flavor,
                "image_uuid": vmInfo.image,
                "dataDisk_offering_uuids": [], 
                "resource_uuid": op_vm_uuid, 
                "l3Network_uuids": l3_network_uuids,
                "default_l3_network_uuid": l3_network_uuids[0],
                "systemTags": [f"staticIp::{l3_network_uuids[-1]}::{ip}"] if ip else []
            }
            if dst_end_points:
                action_params["update_vm_ip_database"] = {
                    "port_id": args.port_id,
                    "device_type": "vm",
                    "op_vm_uuid": op_vm_uuid
                }
                action_params["update_port_mirror_session"] = {
                    "dst_end_point": dst_end_points,
                    "state": 1
                }
            elif src_end_points:
                action_params["create_port_mirror_session"] = {
                    "src_end_points": src_end_points, "port_id": args.port_id, "state": 1}
                action_params["create_vm"].update({"state": 1})
            else:
                action_params["update_vm_ip_database"] = {
                    "port_id": args.port_id, "device_type": "vm", "op_vm_uuid": op_vm_uuid
                }
                action_params["create_vm"].update({"state": 1})
            if vmInfo.is_external == 1:
                action_params["attach_eip_to_vm"] = {
                    "eip_uuid": eip_uuid, 
                    "vm_uuid": vmInfo.vm_uuid
                }
            
            create_transaction(action_params, rangInfo.range_uuid)
        except HTTPException as e:
            raise e from e
        except Exception:
            db.session.rollback()
            print(traceback.print_exc())
            abort(400, message="创建失败")
        return {"code": 200, "info": "开机中..."}

    def power_off(self, vmInfo):
        if vmInfo.type == VmType.container:
            ops.zun_operate.stop_container(vmInfo.op_vm_uuid, timeout=3)
        else:
            action_params= {
                "stop_vm": {
                    "vm_uuid": vmInfo.op_vm_uuid,
                    "state": 1
                }
            }
            create_transaction(action_params, vmInfo.range_uuid)
        return {"code": 200, "info": "关机中,请稍等"}

    def _delete(self, args, vmInfo, rangInfo):
        self.check_role()
        # # 删除安全组
        # for group in vmInfo.security_group_set.all():
        #     self.conn.network.delete_security_group(group.group_id)
        #     group = SecGroup.query.filter_by(group_id=group.group_id).first()
        #     db.session.delete(group)
        action_param = OrderedDict()
        if vmInfo.op_vm_uuid:
            action_param["del_vm_destroy"] = {"vm_uuid": vmInfo.op_vm_uuid}
            action_param["del_vm_expunge"] = {"vm_uuid": vmInfo.op_vm_uuid}
        action_param["del_vm_database"] = {"vm_uuid": vmInfo.vm_uuid, "field": "vm_uuid", "state": 1}
        create_transaction(action_param, vmInfo.range_uuid)
        return {"code": 200, "info": "删除成功"}

    def _detection_scene_environment(self, range_uuid, vmInfo):
        """检测开启虚拟机的场景环境"""
        if not vmInfo.subnet_id:
            return False
        # 检测关联交换机
        switch = switchModel.Switchs.query.filter_by(subnet_id=vmInfo.subnet_id).first()
        if not switch.network_id:
            return False
        return bool(self.load_cloud_object.get_l3_network(vmInfo.subnet_id))
    
    def _inspection_nids_service(self, vmInfo):
        # 查看是否关联nids
        dst_end_points = ""
        subnet_id = vmInfo.subnet_id
        nids_info = rangeModel.RangeVm.query.filter_by(subnet_id=subnet_id, type="NIDS").first()
        if not  nids_info:
            return dst_end_points
        # 排除默认的ip
        port_list = switchModel.Ports.query.filter_by(device_id = nids_info.vm_uuid).all()
        port_ids = [port.port_id for port  in port_list]
        # 获取nids的目标
        nids = self.load_cloud_object.get_vm_details(nids_info.op_vm_uuid)
        # 获取dst 
        if not nids:
            return dst_end_points
        vmNics = nids[0]["vmNics"]
        for  nic  in vmNics:
            uuid = nic["uuid"]
            if  uuid not  in port_ids:
                return uuid
        return dst_end_points
                

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument(
            "action",
            choices=("powerOn", "powerOff", "delete"),
            help="Bad choice: {error_msg}",
            required=True,
        )
        parser.add_argument("vm_uuid", help="虚机vm_uuid不能为空", required=True)
        parser.add_argument("range_uuid", help=" 靶场参数有误", required=True)
        parser.add_argument("port_id", help="端口参数有误")
        parser.add_argument("cidr_list", help="交换机参数有误")
        # 新增参数ip, 将设置ip操作放在创建虚拟机中
        parser.add_argument("ip", help="IP格式错误, 请重新输入")
        args = parser.parse_args()

        range_manager = RangeManager(range_uuid=args["range_uuid"])
        rangInfo = range_manager.range_model
        vmInfo = range_manager.get_vm_from_this(vm_uuid=args["vm_uuid"])

        try:
            # 3、 通过vm_uuid进行后续操作
            # 开机
            if args["action"] == "powerOn":
                # TODO 开机时，如果虚拟机已存在但本地未记录，应该更新记录，如果有记录已开机又开机再给错误提示
                # if vmInfo.type == VmType.container:
                #     return self.power_on_container(args, vmInfo)
                if not self._detection_scene_environment(args.range_uuid, vmInfo):
                    abort(400, message="交换机未创建，请先创建交换机")
                return self.power_on_vm(args, vmInfo, rangInfo)
            # 关机
            if args["action"] == "powerOff":
                return self.power_off(vmInfo)
            # 删除
            if args["action"] == "delete":
                return self._delete(args, vmInfo, rangInfo)

        except ConflictException as e:
            current_app.logger.exception(e)
            abort(400, message="在实例处于 active/stopped 状态时，无法对该实例执行start/stop")
        except ResourceTimeout as e:
            current_app.logger.exception(e)
            abort(400, message="实例处于 transition to active/stopped 状态")
        except (exceptions.HttpException, exceptions.BadRequestException) as e:
            current_app.logger.exception(e)
            abort(e.status_code, message=f"{e.message}")
        except HTTPException as e:
            raise e from e
        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="无法连接到虚拟机")


# 虚拟机快照功能
class VmSnapshot(VmBase):

    # 查询状态
    def get(self, range_uuid, vm_uuid):
        if not all([range_uuid, vm_uuid]):
            abort(400, message="参数错误")

        range_manage = RangeManager(range_uuid=range_uuid)
        vmInfo = range_manage.get_vm_from_this(vm_uuid=vm_uuid)

        info = self.conn.get_image(vmInfo.image_snapshot)
        return {"code": 200, "data": {"image_state": info.status}}

    # 创建快照
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("vm_uuid", help="虚机vm_uuid不能为空", required=True)
        parser.add_argument("range_uuid", help=" 靶场参数有误", required=True)
        args = parser.parse_args()

        range_manage = RangeManager(range_uuid=args["range_uuid"])
        vmInfo = range_manage.get_vm_from_this(vm_uuid=args["vm_uuid"])

        if vmInfo.image_snapshot:
            abort(400, message="该实例已经创建了快照，不能再次创建快照！")

        try:
            # 快照名称
            name = f"{vmInfo.vm_name}快照{str(time.time())}"
            info = self.conn.create_image_snapshot(name=name, server=vmInfo.op_vm_uuid)
            rangeModel.RangeVm.query.filter_by(
                id=vmInfo.id, user_id=self.user.id
            ).update({"image_snapshot": info.id})
            rangeModel.db.session.commit()
            return {"code": 200, "data": "创建成功"}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="无法连接到虚拟机")


# 获取主机详细信息页 cpu 内存 硬盘 网络 等详细信息
class VmInfo(VmBase):

    @property
    def load_cloud_object(self):
        return WebHookManager()

    @classmethod
    def container_handler(cls, args, vmInfo):
        container = ops.zun_operate.get_container_details(vmInfo.op_vm_uuid)
        if not args["get_info_type"]:
            return {
                "code": 200,
                "data": {
                    "name": container.name,
                    "description": container.status_detail,
                    "status": container.status,
                    "addresses": container.addresses,
                    "flavor": {
                        "vcpus": container.cpu,
                        "ram": container.memory,
                        "disk": container.disk,
                    },
                },
            }
        data = ops.zun_operate.get_container_stats(vmInfo.op_vm_uuid)

        data = {
            "cpu": str(data["CPU %"])[:4],
            "mem": str(data["MEM %"])[:4]
        }
        return data

    # my_fields = {'code': fields.Integer, 'data': fields.String}
    # 取得虚拟机控制台地址
    # @marshal_with(my_fields)
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("vm_uuid", help="vm_uuid不能为空", required=True)
        parser.add_argument("get_info_type", help="get_info_type不能为空", required=True)
        parser.add_argument("range_uuid", help=" 靶场range_uuid有误", required=True)
        args = parser.parse_args()

        range_manager = RangeManager(range_uuid=args["range_uuid"])
        vmInfo = range_manager.get_vm_from_this(vm_uuid=args["vm_uuid"])

        if not vmInfo.op_vm_uuid:
            abort(400, message="参数错误")
        try:
            start_time, end_time = Tools.get_timeStamp_scope(15)
            if not args["get_info_type"]:
                return self._extracted_from_vm_info(vmInfo)
            else:
                result = self.load_cloud_object.get_vm_metric(
                    vmInfo.op_vm_uuid, 
                    args["get_info_type"], 
                    start_time,
                    end_time)
                if not result:
                    info = {}
                else:
                    metric_data = result["results"][0]["returnWith"]
                    info = {}
                    for obj, value in metric_data.items():
                        time, data = [], []
                        if isinstance(value, list):
                            for val in value:
                                time.append(Tools.timeStamp_to_strftime(val["time"]))
                                data.append(val["value"])
                            info = {
                                "time": time,
                                "data": data
                                }
                return {
                    "code": 200,
                    "data": info
                }
        except Exception as e:
            current_app.logger.exception(e)
            abort(400, message="获取失败")

    def _extracted_from_vm_info(self, vmInfo):
        # flavor 
        flavor_info = self.load_cloud_object.get_flavors_details(vmInfo.flavor)
        flavor = {
                "name": flavor_info[0]["name"],
                "original_name": flavor_info[0]["name"],
                "is_public": True,
                "vcpus": flavor_info[0]["cpuNum"],
                "ram": flavor_info[0]["memorySize"] / 1024 / 1024 / 1024,
                "cpu_usage": 0
            }
        result = self.load_cloud_object.get_vm_details(vmInfo.op_vm_uuid)
        if not result:
            data = {
                "name": vmInfo.vm_name,
                "description": "",
                "status": "BUILD",
                "addresses": {"vnic1248.0": [{
                    "addr": vmInfo.v4_fixed_ip
                    }]},
                "flavor": flavor
                }
        else:
            inventories = result[0]
            vm_nics = inventories.get("vmNics", [])
            addresses = {
                vm["internalName"]: [
                    {
                        "version": vm["usedIps"][0]["ipVersion"],
                        "addr": vm["ip"],
                        "OS-EXT-IPS:type": vm["type"],
                        "OS-EXT-IPS-MAC:mac_addr": vm["mac"],
                    }
                ]
                for vm in vm_nics
            }
            volume_info = inventories["allVolumes"]
            disk = sum(volume["size"] for volume in volume_info)

            start_time, end_time = Tools.get_timeStamp_scope(1)
            # cpu
            cpu_info = self.load_cloud_object.get_vm_metric(vmInfo.op_vm_uuid, "CPU", start_time, end_time)
            # disk
            disk_info = self.load_cloud_object.get_vm_metric(vmInfo.op_vm_uuid, "DISK", start_time, end_time)
            
            if cpu_info["results"][0]["returnWith"]["zwatch0"]:
                cpu_data = cpu_info["results"][0]["returnWith"]["zwatch0"][-1]
                flavor["cpu_usage"] = "{:.2f}".format(cpu_data["value"] / 100)
            if  disk_info["results"][0]["returnWith"]["zwatch0"]:
                disk_data = disk_info["results"][0]["returnWith"]["zwatch0"]
                disk_data = disk_data[-1] if disk_data else {}
                flavor["disk_usage"] = "{:.2f}".format(disk_data.get("value", 0) / 100)
            flavor.update({
                "disk": disk / 1024 / 1024 / 1024
            })
            data = {
                "name": inventories["name"],
                "description": "",
                "status": "ACTIVE" if inventories["state"] == "Running" else "BUILD",
                "addresses":addresses,
                "flavor": flavor
            }
        return {
            "code": 200,
            "data": {
                "name": vmInfo.vm_name,
                "data": data 
            }
        }


# 靶场下面虚机详细信息
class VmRangeInfos(VmBase):
    container_status_map = {
        "Creating": "BUILD",
        "Created": "BUILD",
        "Stopping": "BUILD",
        "Running": "ACTIVE",
        "Stopped": "SHUTOFF",
    }
    container_build_task_state = ("container_creating", "container_starting", "container_stopping")

    task_status_to_status = {
        "powering-on": "BUILD",  # WAIT_BOOT
        "powering-off": "BUILD",  # "WAIT_SHUTDOWN"
    }
    
    @property
    def load_cloud_object(self):
        return WebHookManager()

    def get(self):
        try:
            params = request.args
            range_uuid = params.get("range_uuid")
            range_manager = RangeManager(range_uuid=range_uuid)
        except PrivateRangeException as e:
            abort(400, e.msg)
            return
        range_info = range_manager.range_model
        data = {
            "topology_json": range_info.rangeInfo[0].topology_json,
            "topology_img": range_info.rangeInfo[0].topology_img,
        }
        return {"code": 200, "data": data}

    def get_container_vm_info(self, vm: RangeVm, vm_info_list: list):
        """获取容器虚拟机信息"""
        try:
            if not vm.op_vm_uuid:
                raise ValueError(f"{vm.vm_name}的op_vm_uuid不存在")
            container = ops.zun_operate.get_container_details(vm.op_vm_uuid)
        except Exception as e:
            current_app.logger.error(f"获取容器{vm.vm_name}失败，对应云平台名称 {vm.op_vm_name}", exc_info=e)
            vm.op_vm_uuid = None
            vm.op_vm_name = None
            db.session.add(vm)
            db.session.commit()
            vm_info_list.append(
                {
                    "id": vm.vm_uuid,
                    "name": vm.vm_name,
                    "description": "",
                    "status": "",
                    "info_snapshot": "",
                    "external": vm.is_external,
                    "flavor": "",
                    "type": vm.type
                }
            )
        else:
            if not container:
                status = ""
            elif container.task_state in self.container_build_task_state:
                status = "BUILD"
            else:
                if not container.task_state and container.status == "Created":
                    try:
                        ops.zun_operate.start_container(container.uuid)
                    except Exception as e:
                        current_app.logger.warning(f"{e}")
                if container.status == "Error":
                    current_app.logger.error(
                        f"容器{vm.vm_name} 异常，请排查openstack及zun，Error: {container.status_reason}")
                status = self.container_status_map.get(container.status, "")

            if not vm.op_vm_uuid and container:
                vm.op_vm_uuid = container.uuid
                db.session.add(vm)
                db.session.commit()

            vm_info_list.append(
                {
                    "id": vm.vm_uuid,
                    "name": vm.vm_name,
                    "description": "",
                    "status": status,
                    "external": vm.is_external,
                    "addresses": container.addresses if container else "",
                    "info_snapshot": "",
                    "flavor": "",
                    "type": vm.type,
                    "container_uuid": vm.op_vm_uuid
                }
            )

    def get_entity_vm_info(self, vm: RangeVm, vm_info_list: list):
        """获取实体虚拟机信息"""
        info_snapshot_status = ""
        if vm.image_snapshot:
            info_snapshot = self.conn.get_image(vm.image_snapshot)
            if info_snapshot:
                info_snapshot_status = info_snapshot.status

        try:
            i = 0
            while i < 5:
                info = self.load_cloud_object.get_vm_details(vm.op_vm_uuid)
                if info:
                    break
                time.sleep(1)
                i += 1
        except exceptions.ResourceNotFound:
            info = None
        if not info:
            self._not_vm_info(vm, vm_info_list)
        else:
            self._exist_vm_info(
                vm, info, vm_info_list, info_snapshot_status
            )

    def _exist_vm_info(self, vm, info, vm_info_list, info_snapshot_status):
            # 虚拟机不存在 op_vm_uuid重置为None
            # 能够获取到信息说明已开机，更新op_vm_uuid
        if not vm.op_vm_uuid:
            vm.op_vm_uuid = info[0]["uuid"]
            db.session.add(vm)
            db.session.commit()
        vm_nics = info[0]["vmNics"]
        address = {}
        for nic in vm_nics:
            internal_name = nic["internalName"]
            nic_dict = {
                    "OS-EXT-IPS-MAC:mac_addr": nic["mac"],
                    "OS-EXT-IPS:type": nic["type"],
                    "addr": nic["ip"],
                    "version": nic["usedIps"][0]["ipVersion"],
                }
            if address.get(internal_name):
                address[internal_name].append(nic_dict)
            else:
                address[internal_name] = [nic_dict]
        if info[0]["state"] == "Running":
            status = "ACTIVE"
        elif info[0]["state"] == "Stopped":
            status = "SHUTOFF"
        else:
            status = "BUILD"


        vm_info_list.append(
            {
                "id": vm.vm_uuid,
                "name": vm.vm_name,
                "description": "",
                "status": status,
                "external": vm.is_external,
                "addresses": address,
                "info_snapshot": info_snapshot_status,
                "flavor": vm.flavor,
                "type": vm.type
            }
        )

    # TODO Rename this here and in `get_entity_vm_info`
    def _not_vm_info(self, vm, vm_info_list):
        vm.op_vm_uuid = None
        vm.op_vm_name = None
        db.session.add(vm)
        db.session.commit()

        data = {
            "id": vm.vm_uuid,
            "name": vm.vm_name,
            "description": "",
            "status": "",
            "info_snapshot": "",
            "external": vm.is_external,
            "flavor": vm.flavor,
            "type": vm.type
        }
        vm_info_list.append(data)

    @counter_time
    def get_vm_info(self, vm_id, vm_info_list):
        vm = RangeVm.query.get(vm_id)
        if vm.op_vm_name:
            self.get_entity_vm_info(vm, vm_info_list)
        else:
            vm_info_list.append(
                {
                    "id": vm.vm_uuid,
                    "name": vm.vm_name,
                    "description": "",
                    "status": "",
                    "info_snapshot": "",
                    "external": vm.is_external,
                    "flavor": vm.flavor,
                    "type": vm.type
                }
            )

        return vm_info_list

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", type=str, help=" 靶场UUID有误", required=True)
        parser.add_argument("vm_uuid", type=str, help=" 虚机UUID有误")
        args = parser.parse_args()
        # 根据uuid靶场下虚拟机list
        try:
            range_manager = RangeManager(range_uuid=args["range_uuid"])
        except PrivateRangeException as e:
            abort(400, e.msg)

        rangeInfo = range_manager.range_model
        range_vm_list = (
            [range_manager.get_vm_from_this(vm_uuid=args["vm_uuid"])]
            if args["vm_uuid"]
            else rangeInfo.vmList
        )

        res_data = {
            "code": 200,
            "data": {
                "rangeInfo": {
                    "topology_json": rangeInfo.rangeInfo[0].topology_json,
                    "topology_img": rangeInfo.rangeInfo[0].topology_img,
                },
                "vm_list": "",
            },
        }
        if not range_vm_list:
            return res_data
        vm_info_list = []
        try:
            # 避免wait可能出现的无限等待
            g_queue_name = f"get_vm_info_{random.random()}"
            for vm in range_vm_list:
                vthread.pool(32, gqueue=g_queue_name)(self.get_vm_info)(vm.id, vm_info_list)
            vthread.pool.wait(gqueue=g_queue_name)
            vthread.pool.close_by_gqueue(gqueue=g_queue_name)

        except Exception as e:
            current_app.logger.exception(e)

        res_data["data"].update({"vm_list": vm_info_list})
        return res_data


# 进入虚机
class VmEnter(VmBase):
    
    @property
    def load_cloud_object(self):
        return WebHookManager()
    
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("vm_uuid", help="vm_uuid不能为空", required=True)
        parser.add_argument("range_uuid", help=" 靶场range_uuid有误", required=True)
        parser.add_argument("is_red_blue", default=0, type=int, required=False)
        args = parser.parse_args()

        range_manger = RangeManager(range_uuid=args["range_uuid"], ignore_private=True)
        vmInfo = range_manger.get_vm_from_this(vm_uuid=args["vm_uuid"])

        if not vmInfo:
            abort(400, message="虚拟机不存在")

        if vmInfo.type == VmType.container:
            abort(400, message="容器虚拟机无法使用该接口")

        if not vmInfo.op_vm_uuid:
            abort(400, message="请先将虚拟机开机")

        try:
            # 获取noVnc地址
            result = self.load_cloud_object.get_vm_console_address(vmInfo.op_vm_uuid)
            localtion = result["location"]
            index = 0
            console_url = current_app.config["ZSTACK_CLOUD"]["console_url"]
            while index < 5:
                time.sleep(3)
                response = requests.get(url=localtion)
                if response.status_code == 200:
                    data = json.loads(response.text)["inventory"]
                    hostname = data["hostname"]
                    port = data["port"]
                    token = data["token"]
                    url = f"{console_url}/novnc?host={hostname}&port={port}&token={token}&title={vmInfo.vm_name}&vmCdRoms=true&language=zh-CN&platform={vmInfo.type}&ip={vmInfo.v4_fixed_ip}&guestToolsInstalled=false&lowVersion=false"
                    break
                index += 1
            info = {"url": url}          
        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="获取失败")
        else:
            return {"code": 200, "data": info}


# 获取镜像列表
# 镜像定义在配置文件中
class VmImageList(VmBase):
    def get(self):
        try:
            image_infos = Image.query.filter(
                Image.status != "0", Image.image_id.isnot(None)
            ).all()
            info = [
                {
                    "id": image.image_id,
                    "name": image.image_name,
                    "type": image.image_type,
                    "container_format": image.container_format,
                }
                for image in image_infos
            ]
            return {"code": 200, "data": info}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="获取失败")


# 创建网络  暂停使用此接口, 用户靶场唯一range_uuid
class VmNetwork(VmBase):
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument(
            "range_uuid", type=str, help="靶场range_uuid有误", required=True
        )
        args = parser.parse_args()

        if not all([args["range_uuid"]]):
            abort(400, message="参数错误")

        # 检测参数是否正确
        rangLinkModuleInfo = self.getMyRangeByUUID(range_uuid=args["range_uuid"])
        if not rangLinkModuleInfo:
            abort(400, message="参数错误")

        # 假如靶场有网络id不能创建
        if rangLinkModuleInfo.network_id and rangLinkModuleInfo.network_id != 0:
            abort(400, message="靶场有网络id，不能创建")

        netName = f"net-{str(rangLinkModuleInfo.id)}"
        netSubnetName = f"net-{str(rangLinkModuleInfo.id)}-subnet"

        try:
            example_network = self.conn.network.create_network(name=netName)
            example_subnet = self.conn.network.create_subnet(  # noqa
                name=netSubnetName,
                network_id=example_network.id,
                ip_version="4",
                cidr="192.168.2.0/24",
                gateway_ip="192.168.2.1",
            )

            rangeModel.RangeLinkModule.query.filter_by(id=rangLinkModuleInfo.id).update(
                {"network_id": example_network.id}
            )
            rangeModel.db.session.commit()
        except Exception as e:
            rangeModel.db.session().rollback()
            current_app.logger.exception(e)
            abort(500, message="获取失败")

        return {"code": 200, "data": "创建成功"}


# 删除靶场
class VmEnv(VmBase):

    def delete(self):
        # 删除靶场
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", help="靶场信息有误", required=True)
        args = parser.parse_args()

        range_manager = RangeManager(range_uuid=args["range_uuid"])
        myRangeInfo: rangeModel.RangeLinkModule = range_manager.range_model

        if myRangeInfo is None:
            abort(400, message="靶场不存在")

        # 查询靶场ID
        rangeInfo = myRangeInfo.Range

        link_type, link_obj = range_manager.get_linked_info()
        if link_type:
            abort(400, message=f"该靶场由{link_type.label}: {link_obj.name} 使用中，不允许删除")

        if range_manager.range_model.user_id != self.user.id and not self.is_admin:
            abort(400, message="仅允许删除自己创建的场景")
        
        if not myRangeInfo.network_id:
            # 删除数据库
            try:
                range_info = rangeModel.Range.query.join(
                    rangeModel.RangeLinkModule, rangeModel.RangeLinkModule.range_id == rangeModel.Range.id
                ).filter_by(range_uuid=args.range_uuid).first()

                if range_info is None:
                    abort(400, message="靶场不存在")
                # 清除对应数据库
                db.session.delete(range_info)
                db.session.commit() 
                return {"code": 200, "data": "删除成功"}
            except Exception:
                db.session.rollback()
                abort(400, message="删除失败")
        action_params = OrderedDict()
        if rangeInfo:
            # 删除虚拟机
            for vm in myRangeInfo.vmList:
                if vm.op_vm_uuid:
                    action_params[f"del_vm_destroy-{vm.op_vm_uuid}"] = {"vm_uuid": vm.op_vm_uuid}
                    action_params[f"del_vm_expunge-{vm.op_vm_uuid}"] = {"vm_uuid": vm.op_vm_uuid}
                # 删除虚拟ip
                if vm.eip_uuid:
                    action_params[f"del_vips_ip-{vm.op_vm_uuid}"] = {"vip_uuid": vm.eip_uuid}
            
            # 删除路由器
            route_list = switchModel.Routes.query.filter_by(range_uuid=args["range_uuid"]).all()
            for route in  route_list:
                action_params[f"del_vm_destroy-{route.op_route}"] = {"vm_uuid": route.op_route}

            # 删除网络(交换机、基础网络)
            action_params["del_l2_network"] = {"network_id": myRangeInfo.network_id}
            action_params["del_range_database"] = {"range_uuid": args.range_uuid, "state": 1}
            create_transaction(action_params, args.range_uuid)
        else:
            abort(401, message="权限错误")
        return {"code": 200, "data": "删除成功"}


class TruncateTablesError(Exception):
    """清空数据表失败"""


# 恢复出厂设置
class RestoreFactorySettings(VmBase):
    def post(self):
        if self.user.is_superuser != 1:
            abort(401, message="权限错误")

        start_time = time.time()
        # 删除实例主机，网络，虚拟机
        from scripts.releaseSpace import RangeVmBase

        rangeVmB = RangeVmBase()
        if not rangeVmB.restoreFactorySettings():
            abort(500, message="删除虚拟机失败")

        system_courses = courseModel.Course.query.filter_by(isSystem=1).all()
        courses = [_.to_dict() for _ in system_courses]

        # 清空数据表
        if not self.truncate_tables():
            abort(500, message="清空数据表失败")

        try:
            # 添加系统管理员
            os.system("flask init_permission -sw y")

            # 初始化镜像
            os.system("flask init_image")

            # 添加系统靶场
            os.system("flask add_range")

            # 添加系统赛题及内置赛题
            os.system("flask add_issue")

        except Exception as e:
            abort(500, message=str(e))

        # 添加内置课程
        from utils.initial_db import add_system_course

        if not add_system_course(courses):
            abort(500, message="系统课件重置失败")

        return {"code": 200, "data": "重置成功", "use_time": time.time() - start_time}

    @staticmethod
    def truncate_tables():
        try:
            res = db.session.execute(
                """
                SELECT CONCAT('truncate TABLE ', table_schema, '.', TABLE_NAME, ';')
                FROM INFORMATION_SCHEMA.TABLES where table_schema in ('virtualdb');
                """
            ).fetchall()
            db.session.execute("SET FOREIGN_KEY_CHECKS = 0;")
            for execute_sql in res:
                db.session.execute(execute_sql[0])
            db.session.execute("SET FOREIGN_KEY_CHECKS = 1;")
            return True
        except TruncateTablesError as e:
            current_app.logger.exception(e)
            return False

    @staticmethod
    def rm_courseware_path():
        """清除本地课件文件"""
        import shutil

        courseware_path = os.path.abspath("app/api/v1/organizer/path")
        if os.path.exists(courseware_path):
            shutil.rmtree(courseware_path)


class SecurityGroup(VmBase):
    
    @property
    def load_cloud_object(self):
        return WebHookManager()
    
    def get(self, group_id):
        group = self.conn.get_security_group(group_id)
        return {"code": 200, "data": group}

    def post(self):
        parser = reqparse.RequestParser()
        # parser.add_argument("vm_uuid", help="虚拟机id不能为空", required=True)
        parser.add_argument("name", type=is_Chinese, required=True)
        parser.add_argument("description", type=is_Chinese, default="")
        parser.add_argument("range_uuid", help="场景id不能为空", required=True)
        args = parser.parse_args()

        # if self.conn.get_security_group(args.name):
        if rangeModel.SecurityGroup.query.filter_by(range_uuid=args.range_uuid, security_group_name=args.name).first():
            abort(500, message="安全组名称已存在")

        # vm_info = rangeModel.RangeVm.query.filter_by(vm_uuid=args.vm_uuid).first()
        # if not vm_info.op_vm_uuid:
        #     abort(500, message="虚拟机开机后才能关联安全组")

        # quota_values = self.conn.network.get_quota(self.user.project_id)
        # groups, group = list(self.conn.network.security_groups()), {}
        # groups_rules = list(self.conn.network.security_group_rules())
        # if len(groups) >= quota_values.security_groups:
        #     abort(
        #         500,
        #         message=f"超过[“安全组”]资源的配额：最大数量{quota_values.security_groups}",
        #     )

        # if quota_values.security_group_rules - len(groups_rules) < 2:
        #     abort(
        #         500,
        #         message=f"[“安全组规则”]的最大配额为{quota_values.security_group_rules}, 当前资源不支持创建安全组规则",
        #     )
        try:
            group = rangeModel.SecurityGroup(
                security_group_uuid=Tools.get_uuid4_no_line(),
                security_group_name=args.name,
                description=args.description,
                range_uuid=args.range_uuid
            )
            self.load_cloud_object.create_security_group(group.security_group_name, group.security_group_uuid, group.description)
            db.session.add(group)
            db.session.commit()
            return {"code": 200, "data": {"security_group_id": group.id}}

        except Exception as e:
            db.session.rollback()
            current_app.logger.exception(e)
            abort(500, message="创建失败", error=str(e))

    def put(self, group_id):
        parser = reqparse.RequestParser()
        parser.add_argument("name", type=is_Chinese, required=True)
        parser.add_argument("description", type=is_Chinese, default="")
        args = parser.parse_args()

        # group = self.conn.get_security_group(args.name)
        # if group and group.id != group_id:
        #     abort(500, message="安全组名称已存在")

        try:
            group = self.conn.network.update_security_group(
                group_id, name=args.name, description=args.description
            )
            return {"code": 200, "data": {"security_group_id": group.id}}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="更新失败", error=str(e))

    def delete(self, group_id):
        # parser = reqparse.RequestParser()
        # parser.add_argument("vm_uuid", help="虚拟机id不能为空", required=True)
        # args = parser.parse_args()  # noqa

        try:
            # vm_info = rangeModel.RangeVm.query.filter_by(vm_uuid=args.vm_uuid).first()
            # if not vm_info.op_vm_uuid:
            #     abort(500, message="参数错误")
            # server = self.conn.get_server(vm_info.op_vm_uuid)
            # if self.conn.compute.fetch_server_security_groups(server):
            #     abort(500, message="安全组已关联，无法删除")
            self.load_cloud_object.del_security_group(group_id)
        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="安全组已关联，无法删除", error=str(e))
        return {"code": 200, "data": "删除成功"}


class SecurityGroupList(VmBase):
    def get(self, vm_uuid):
        default_group = self.conn.get_security_group("default")
        data = [default_group]
        vm_info = rangeModel.RangeVm.query.filter_by(vm_uuid=vm_uuid).first()
        security_group_set = vm_info.security_group_set.all()
        data.extend(
            self.conn.get_security_group(group.group_id)
            for group in security_group_set
        )
        return {"code": 200, "data": data}


class SecurityGroupAssociated(VmBase):
    
    @property
    def load_cloud_object(self):
        return WebHookManager()
    
    def get(self, vm_uuid):
        vm_info = rangeModel.RangeVm.query.filter_by(vm_uuid=vm_uuid).first()
        if not vm_info.op_vm_uuid:
            abort(500, message="虚拟机开机后才能关联安全组")
        group = self.conn.compute.fetch_server_security_groups(vm_info.op_vm_uuid)
        return {"code": 200, "data": group.security_groups}

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("vm_uuid", required=True, help="虚拟机不能为空")
        parser.add_argument("security_group_uuid", required=True, help="关联安全组不能为空")
        parser.add_argument("range_uuid", required=True, help="场景uuid")
        args = parser.parse_args()
        vm_info = rangeModel.RangeVm.query.filter_by(vm_uuid=args.vm_uuid).first()
        if not vm_info.op_vm_uuid:
            abort(500, message="虚拟机开机后才能关联安全组")
        try:
            group_info = rangeModel.SecurityGroup.query.filter_by(
                security_group_uuid=args.security_group_uuid, range_uuid=args.range_uuid).first()
            if not group_info:
                abort(400, message="安全组不存在")
            # 获取关联网卡
            port_info = switchModel.Ports.query.filter_by(device_id=args.vm_uuid, device_owner="vm").all()
            vm_nic_uuids = [port.port_id for port  in port_info]
            subnet_id = port_info[0].subnet_id
            for vm_nic in vm_nic_uuids:
                associate_obj = rangeModel.SecurityGroupAssociate(
                    vm_uuid=args.vm_uuid,
                    range_uuid=args.range_uuid,
                    vm_nic_uuid=vm_nic,
                    security_group_uuid=args.security_group_uuid
                )
                db.session.add(associate_obj)
            db.session.commit()
            self.load_cloud_object.attach_security_group_l3_network(args.security_group_uuid, subnet_id, vm_nic_uuids)
            return {"code": 200, "data": "关联成功"}

        except Exception as e:
            current_app.logger.exception(e)
            db.session.rollback()
            abort(500, message="关联失败", error=str(e))


class SecurityGroupRule(VmBase):
    
    @property
    def load_cloud_object(self):
        return WebHookManager()
    
    def get(self, rule_id):
        try:
            data = self.conn.network.get_security_group_rule(rule_id)
            self.load_cloud_object.get_security_group_rule()
            return {"code": 200, "data": data}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="参数错误")

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("security_group_id", help="安全组id不能为空", required=True)
        parser.add_argument("remote_group_id")
        parser.add_argument("direction")
        parser.add_argument("protocol", help="协议不能为空", required=True)
        parser.add_argument("ethertype", help="以太网类型不能为空", required=True)
        parser.add_argument("port_range_min")
        parser.add_argument("port_range_max")
        parser.add_argument("remote_ip_prefix", help="路由标记不能为空", required=True)
        parser.add_argument("description", type=is_Chinese, default="")
        args = parser.parse_args()

        quota_values = self.conn.network.get_quota(self.user.project_id)
        groups_rules = list(self.conn.network.security_group_rules())

        if len(groups_rules) >= quota_values.security_group_rules:
            abort(
                500,
                message=f"[“安全组规则”]的最大配额为{quota_values.security_group_rules}, 当前资源不支持创建安全组规则",
            )

        try:
            direction = args.direction.capitalize()
            protocol, port_range_min, port_range_max = update_rule_body(args)
            resource_uuid = Tools.get_uuid4_no_line()
            self.load_cloud_object.attach_security_group_rule(
                args.security_group_id, 
                direction, 
                port_range_min, 
                port_range_max, 
                protocol,
                args.remote_ip_prefix,
                resource_uuid
            )
            return {"code": 200, "data": {"rule_id": resource_uuid}}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="创建失败", error=str(e))

    def delete(self, rule_id):
        try:
            # self.conn.network.delete_security_group_rule(rule_id)
            self.load_cloud_object.del_security_group_rule(
                rule_id
            )            
            return {"code": 200, "data": "删除成功"}
        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="参数错误", error=str(e))


class DateEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.strftime("%Y-%m-%d %H:%M:%S")
        else:
            return json.JSONEncoder.default(self, obj)


class ActiveDeleteVm(VmBase):
    @staticmethod
    def get_activate_range_vm(range_id):
        rangVmList = []
        rang_activate_List = []
        if range_id:
            rang_activate_List = rangeModel.ActiveRangeVm.query.filter_by(
                range_uuid=range_id
            ).all()

        for rang_activave_vm in rang_activate_List:
            vm_id = rang_activave_vm.op_vm_uuid
            if vm_id:
                rangVmList.append(vm_id)

        return rangVmList

    @staticmethod
    def get_activate_range(range_id):
        return rangeModel.ActiveRange.query.filter_by(range_uuid=range_id).all()

    def delete(self):
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", help="靶场信息有误", required=True)
        args = parser.parse_args()
        range_uuid = args.get("range_uuid")
        if range_uuid is None:
            abort(400, message="参数错误")

        rangeVmList = rangeModel.ActiveRangeVm.query.filter_by(
            range_uuid=range_uuid
        ).all()
        range_list = rangeModel.ActiveRange.query.filter_by(range_uuid=range_uuid).all()
        print("ac-range:", rangeVmList)
        if not rangeVmList:
            return {"code": 200, "data": "没有虚拟机要删除"}
        acticve_range_uuid = [range.activeRange_range_uuid for range in range_list]
        action_params = OrderedDict()
        try:
            for vm in rangeVmList:
                if vm.op_vm_uuid:
                    action_params[f"del_vm_destroy-{vm.op_vm_uuid}"] = {"vm_uuid": vm.op_vm_uuid}
                    action_params[f"del_vm_expunge-{vm.op_vm_uuid}"] = {"vm_uuid": vm.op_vm_uuid}
                # 删除虚拟ip
                if vm.is_external:
                    action_params[f"del_vips_ip-{vm.op_vm_uuid}"] = {"vip_uuid": vm.eip_uuid}   
            # 删除路由器
            route_list = db.session.query(switchModel.ActiveRoutes).filter(switchModel.ActiveRoutes.range_uuid.in_(acticve_range_uuid)).all()
            for route in route_list:
                action_params[f"del_vm_destroy-{route.op_route}"] = {"vm_uuid": route.op_route}

            for range_active in range_list:
                action_params["del_l2_network"] = {"network_id": range_active.network_id}
                db.session.delete(range_active)
            create_transaction(action_params, range_uuid)
            db.session.commit()
            return {"code": 200, "data": "删除成功"}
        except Exception as e:
            db.session.rollback()
            current_app.logger.exception(e)
            abort(500, message="参数错误", error=str(e))


class RouterManage(VmBase):
    def get(self, router_uuid):
        router_info = rangeModel.RangeRouter.query.filter_by(
            router_uuid=router_uuid
        ).first()
        if not router_info:
            abort(400, message="路由不存在")
        return {"code": 200, "data": router_info.to_dict()}

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("router_name", type=str, help="路由名不能为空", required=True)
        parser.add_argument(
            "status", help="路由状态", choices=("0", "1", "2"), required=True
        )
        parser.add_argument("range_uuid", type=str, help="UUID不能为空", required=True)
        args = parser.parse_args()

        if not all([args["router_name"], args["status"], args["range_uuid"]]):
            abort(400, message="参数错误")

        range_info = self.getMyRangeByUUID(range_uuid=args["range_uuid"])
        if not range_info:
            abort(400, message="靶场不存在")

        try:
            with db.auto_commit():
                obj = rangeModel.RangeRouter(
                    router_name=args["router_name"],
                    status=args["status"],
                    range_uuid=args["range_uuid"],
                    user_id=self.user.id,
                )
                db.session.add(obj)
                db.session.commit()
                return {"code": 200, "data": obj.to_dict()}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="系统错误")

    def put(self):
        parser = reqparse.RequestParser()
        parser.add_argument("router_uuid", type=str, help="路由uuid不能为空", required=True)
        parser.add_argument("router_name", type=str, help="路由名不能为空", required=True)
        parser.add_argument(
            "status", help="路由状态", choices=("0", "1", "2"), required=True
        )
        args = parser.parse_args()

        if not all([args["router_name"], args["status"], args["router_uuid"]]):
            abort(400, message="参数错误")

        try:
            with db.auto_commit():
                rangeModel.RangeRouter.query.filter_by(
                    router_uuid=args["router_uuid"]
                ).update(args)
                return {"code": 200, "data": "修改成功"}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="系统错误")

    def delete(self, router_uuid):
        router_info = rangeModel.RangeRouter.query.filter_by(
            router_uuid=router_uuid
        ).first()
        if not router_info:
            abort(400, message="router not exist")

        with db.auto_commit():
            db.session.delete(router_info)
            return {"code": 200, "data": "删除成功"}


class EncryptsManage(VmBase):
    def get(self, id):
        encrypt_info = rangeModel.EncryptConfig.query.filter_by(id=id).first()
        if not encrypt_info:
            abort(400, message="纵向加密不存在")
        return {"code": 200, "data": encrypt_info.to_dict()}

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("gateway_name", type=str, help="网关名称不能为空", required=True)
        parser.add_argument("gateway_address", type=check_ip, required=True)
        parser.add_argument("remote_address", type=check_ip, required=True)
        parser.add_argument(
            "system_type",
            choices=("装置管理", "日志审计", "远程调试"),
            help="Bad choice: {error_msg}",
            required=True,
        )
        parser.add_argument(
            "certificate",
            choices=("一级CA证书", "二级CA证书", "主操作员证书", "备操作员证书", "装置管理系统证书"),
            help="Bad choice: {error_msg}",
            required=True,
        )
        parser.add_argument("router_uuid", type=str, help="路由uuid不能为空", required=True)
        args = parser.parse_args()

        if not all(
                [
                    args["gateway_name"],
                    args["gateway_address"],
                    args["remote_address"],
                    args["system_type"],
                    args["certificate"],
                    args["router_uuid"],
                ]
        ):
            abort(400, message="参数错误")

        if not rangeModel.RangeRouter.query.filter_by(
                router_uuid=args["router_uuid"]
        ).all():
            abort(400, message="router not exist")

        try:
            with db.auto_commit():
                obj = rangeModel.EncryptConfig(
                    gateway_name=args["gateway_name"],
                    gateway_address=args["gateway_address"],
                    remote_address=args["remote_address"],
                    system_type=args["system_type"],
                    certificate=args["certificate"],
                    router_uuid=args["router_uuid"],
                )
                db.session.add(obj)
                db.session.commit()
                return {"code": 200, "data": obj.to_dict()}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="系统错误")

    def put(self):
        parser = reqparse.RequestParser()
        parser.add_argument("gateway_name", type=str, help="网关名称不能为空", required=True)
        parser.add_argument("gateway_address", type=check_ip, required=True)
        parser.add_argument("remote_address", type=check_ip, required=True)
        parser.add_argument(
            "system_type",
            choices=("装置管理", "日志审计", "远程调试"),
            help="Bad choice: {error_msg}",
            required=True,
        )
        parser.add_argument(
            "certificate",
            choices=("一级CA证书", "二级CA证书", "主操作员证书", "备操作员证书", "装置管理系统证书"),
            help="Bad choice: {error_msg}",
            required=True,
        )
        parser.add_argument("id", type=int, help="纵向加密id不能为空", required=True)
        args = parser.parse_args()

        if not all(
                [
                    args["gateway_name"],
                    args["gateway_address"],
                    args["remote_address"],
                    args["system_type"],
                    args["certificate"],
                    args["id"],
                ]
        ):
            abort(400, message="参数错误")

        try:
            with db.auto_commit():
                rangeModel.EncryptConfig.query.filter_by(id=args["id"]).update(args)
                return {"code": 200, "data": "修改成功"}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="系统错误")

    def delete(self, id):
        encrypt_info = rangeModel.EncryptConfig.query.filter_by(id=id).first()
        if not encrypt_info:
            abort(400, message="参数错误")

        with db.auto_commit():
            db.session.delete(encrypt_info)
            return {"code": 200, "data": "删除成功"}


class EncryptsDisassociate(VmBase):
    def delete(self):
        params = request.json
        encrypt_ids = params.get("encrypt_ids", [])
        if not all([encrypt_ids]):
            abort(400, message="参数错误")

        for encrypt_id in encrypt_ids:
            encrypt_info = rangeModel.EncryptConfig.query.get(encrypt_id)
            if not encrypt_info:
                abort(400, message="纵向加密信息不存在")
            try:
                db.session.delete(encrypt_info)
                db.session.commit()

            except Exception as e:
                current_app.logger.exception(e)
                db.session.rollback()
                abort(500, message="删除失败", error=str(e))

        return {"code": 200, "data": "删除成功"}


class StrategiesManage(VmBase):
    def get(self, id):
        strategy_info = rangeModel.StrategyConfig.query.filter_by(id=id).first()
        if not strategy_info:
            abort(400, message="加密策略不存在")
        return {"code": 200, "data": strategy_info.to_dict()}

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("name", help="网关名称不能为空", required=True)
        parser.add_argument(
            "ether_type", help="协议类型错误", choices=("TCP", "UDP"), required=True
        )
        parser.add_argument("in_start_address", type=check_ip, required=True)
        parser.add_argument("in_end_address", type=check_ip, required=True)
        parser.add_argument("ex_start_address", type=check_ip, required=True)
        parser.add_argument("ex_end_address", type=check_ip, required=True)
        parser.add_argument(
            "in_port_range_max", type=check_port, help="内网起始端口错误", required=True
        )
        parser.add_argument(
            "in_port_range_min", type=check_port, help="内网终止端口错误", required=True
        )
        parser.add_argument(
            "ex_port_range_max", type=check_port, help="外网起始端口错误", required=True
        )
        parser.add_argument(
            "ex_port_range_min", type=check_port, help="外网终止端口错误", required=True
        )
        parser.add_argument("encrypt_id", type=int, help="纵向加密id错误", required=True)
        args = parser.parse_args()

        if not all([args["name"]]):
            abort(400, message="网关名称不能为空")

        if not rangeModel.EncryptConfig.query.filter_by(id=args["encrypt_id"]).all():
            abort(400, message="纵向加密信息不存在")

        try:
            with db.auto_commit():
                obj = rangeModel.StrategyConfig(
                    name=args["name"],
                    ether_type=args["ether_type"],
                    in_start_address=args["in_start_address"],
                    in_end_address=args["in_end_address"],
                    ex_start_address=args["ex_start_address"],
                    ex_end_address=args["ex_end_address"],
                    in_port_range_max=args["in_port_range_max"],
                    in_port_range_min=args["in_port_range_min"],
                    ex_port_range_max=args["ex_port_range_max"],
                    ex_port_range_min=args["ex_port_range_min"],
                    encrypt_id=args["encrypt_id"],
                )
                db.session.add(obj)
                db.session.commit()
                return {"code": 200, "data": obj.to_dict()}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="系统错误")

    def put(self):
        parser = reqparse.RequestParser()
        parser.add_argument("name", help="网关名称不能为空", required=True)
        parser.add_argument(
            "ether_type", help="协议类型错误", choices=("TCP", "UDP"), required=True
        )
        parser.add_argument("in_start_address", type=check_ip, required=True)
        parser.add_argument("in_end_address", type=check_ip, required=True)
        parser.add_argument("ex_start_address", type=check_ip, required=True)
        parser.add_argument("ex_end_address", type=check_ip, required=True)
        parser.add_argument(
            "in_port_range_max", type=check_port, help="内网起始端口不能为空", required=True
        )
        parser.add_argument(
            "in_port_range_min", type=check_port, help="内网终止端口不能为空", required=True
        )
        parser.add_argument(
            "ex_port_range_max", type=check_port, help="外网起始端口不能为空", required=True
        )
        parser.add_argument(
            "ex_port_range_min", type=check_port, help="外网终止端口不能为空", required=True
        )
        parser.add_argument("id", type=int, help="策略id不能为空", required=True)
        args = parser.parse_args()

        if not all(
                [
                    args["name"],
                    args["ether_type"],
                    args["in_start_address"],
                    args["in_end_address"],
                    args["ex_start_address"],
                    args["ex_end_address"],
                    args["in_port_range_max"],
                    args["in_port_range_min"],
                    args["ex_port_range_max"],
                    args["ex_port_range_min"],
                    args["id"],
                ]
        ):
            abort(400, message="参数错误")

        try:
            with db.auto_commit():
                rangeModel.StrategyConfig.query.filter_by(id=args["id"]).update(args)
                return {"code": 200, "data": "修改成功"}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="系统错误")

    def delete(self, id):
        strategy_info = rangeModel.StrategyConfig.query.filter_by(id=id).first()
        if not strategy_info:
            abort(400, message="参数错误")

        with db.auto_commit():
            db.session.delete(strategy_info)
            return {"code": 200, "data": "删除成功"}


class GatekeepersManage(VmBase):
    def get(self, id):
        gatekeeper_info = rangeModel.Gatekeeper.query.filter_by(id=id).first()
        if not gatekeeper_info:
            abort(400, message="网闸不存在")
        return {"code": 200, "data": gatekeeper_info.to_dict()}

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("name", help="名称不能为空", required=True)
        parser.add_argument("description", default="")
        parser.add_argument("router_uuid", type=str, help="路由uuid不能为空", required=True)
        args = parser.parse_args()

        if not all([args["name"], args["router_uuid"]]):
            abort(400, message="参数错误")

        if not rangeModel.RangeRouter.query.filter_by(
                router_uuid=args["router_uuid"]
        ).all():
            abort(400, message="router not exist")

        try:
            with db.auto_commit():
                obj = rangeModel.Gatekeeper(
                    name=args["name"],
                    description=args["description"],
                    router_uuid=args["router_uuid"],
                )
                db.session.add(obj)
                db.session.commit()
                return {"code": 200, "data": obj.to_dict()}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="系统错误")

    def put(self):
        parser = reqparse.RequestParser()
        parser.add_argument("id", type=int, help="网闸id不能为空", required=True)
        parser.add_argument("name", help="名称不能为空", required=True)
        parser.add_argument("description", default="")
        args = parser.parse_args()

        if not all([args["id"], args["name"]]):
            abort(400, message="参数错误")

        try:
            with db.auto_commit():
                rangeModel.Gatekeeper.query.filter_by(id=args["id"]).update(args)
                return {"code": 200, "data": "修改成功"}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="系统错误")

    def delete(self, id):
        gatekeeper_info = rangeModel.Gatekeeper.query.filter_by(id=id).first()
        if not gatekeeper_info:
            abort(400, message="参数错误")

        with db.auto_commit():
            db.session.delete(gatekeeper_info)
            return {"code": 200, "data": "删除成功"}


class GatekeepersDisassociated(VmBase):
    def delete(self):
        params = request.json
        gatekeeper_ids = params.get("gatekeeper_ids", [])
        if not all([gatekeeper_ids]):
            abort(400, message="参数错误")

        for gatekeeper_id in gatekeeper_ids:
            gatekeeper_info = rangeModel.Gatekeeper.query.get(gatekeeper_id)
            if not gatekeeper_info:
                abort(400, message="网闸信息不存在")
            try:
                db.session.delete(gatekeeper_info)
                db.session.commit()

            except Exception as e:
                current_app.logger.exception(e)
                db.session.rollback()
                abort(500, message="删除失败", error=str(e))

        return {"code": 200, "data": "删除成功"}


class GatekeeperRulesManage(VmBase):
    def get(self, id):
        gatekeeper_rule_info = rangeModel.GatekeeperRule.query.filter_by(id=id).first()
        if not gatekeeper_rule_info:
            abort(400, message="网闸规则不存在")
        return {"code": 200, "data": gatekeeper_rule_info.to_dict()}

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("name", help="网闸规则名称不能为空", required=True)
        parser.add_argument(
            "ether_type", help="协议类型错误", choices=("TCP", "UDP"), required=True
        )
        parser.add_argument("in_real_address", type=check_ip, required=True)
        parser.add_argument("in_virtual_address", type=check_ip, required=True)
        parser.add_argument("in_virtual_mask", type=check_ip, required=True)
        parser.add_argument(
            "in_port_range_min", type=check_port, help="内网终止端口错误", required=True
        )
        parser.add_argument(
            "in_port_range_max", type=check_port, help="内网起始端口错误", required=True
        )
        parser.add_argument(
            "in_gateway_address", type=check_ip, help="外网起始端口错误", required=True
        )
        parser.add_argument("ex_real_address", type=check_ip, required=True)
        parser.add_argument("ex_virtual_address", type=check_ip, required=True)
        parser.add_argument("ex_virtual_mask", type=check_ip, required=True)
        parser.add_argument(
            "ex_port_range_min", type=check_port, help="内网终止端口错误", required=True
        )
        parser.add_argument(
            "ex_port_range_max", type=check_port, help="内网起始端口错误", required=True
        )
        parser.add_argument(
            "ex_gateway_address", type=check_ip, help="外网起始端口错误", required=True
        )
        parser.add_argument("gatekeeper_id", type=int, help="网闸id错误", required=True)
        args = parser.parse_args()

        if not all([args["name"]]):
            abort(400, message="网闸规则名称不能为空")

        if not rangeModel.Gatekeeper.query.filter_by(id=args["gatekeeper_id"]).all():
            abort(400, message="网闸信息不存在")

        try:
            with db.auto_commit():
                obj = rangeModel.GatekeeperRule(
                    name=args["name"],
                    ether_type=args["ether_type"],
                    in_real_address=args["in_real_address"],
                    in_virtual_address=args["in_virtual_address"],
                    in_virtual_mask=args["in_virtual_mask"],
                    in_port_range_min=args["in_port_range_min"],
                    in_port_range_max=args["in_port_range_max"],
                    in_gateway_address=args["in_gateway_address"],
                    ex_real_address=args["ex_real_address"],
                    ex_virtual_address=args["ex_virtual_address"],
                    ex_virtual_mask=args["ex_virtual_mask"],
                    ex_port_range_min=args["ex_port_range_min"],
                    ex_port_range_max=args["ex_port_range_max"],
                    ex_gateway_address=args["ex_gateway_address"],
                    gatekeeper_id=args["gatekeeper_id"],
                )
                db.session.add(obj)
                db.session.commit()
                return {"code": 200, "data": obj.to_dict()}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="系统错误")

    def put(self):
        parser = reqparse.RequestParser()
        parser.add_argument("name", help="网闸规则名称不能为空", required=True)
        parser.add_argument(
            "ether_type", help="协议类型错误", choices=("TCP", "UDP"), required=True
        )
        parser.add_argument("in_real_address", type=check_ip, required=True)
        parser.add_argument("in_virtual_address", type=check_ip, required=True)
        parser.add_argument("in_virtual_mask", type=check_ip, required=True)
        parser.add_argument(
            "in_port_range_min", type=check_port, help="内网终止端口错误", required=True
        )
        parser.add_argument(
            "in_port_range_max", type=check_port, help="内网起始端口错误", required=True
        )
        parser.add_argument(
            "in_gateway_address", type=check_ip, help="外网起始端口错误", required=True
        )
        parser.add_argument("ex_real_address", type=check_ip, required=True)
        parser.add_argument("ex_virtual_address", type=check_ip, required=True)
        parser.add_argument("ex_virtual_mask", type=check_ip, required=True)
        parser.add_argument(
            "ex_port_range_min", type=check_port, help="内网终止端口错误", required=True
        )
        parser.add_argument(
            "ex_port_range_max", type=check_port, help="内网起始端口错误", required=True
        )
        parser.add_argument(
            "ex_gateway_address", type=check_ip, help="外网起始端口错误", required=True
        )
        parser.add_argument("id", type=int, help="网闸股则id错误", required=True)
        args = parser.parse_args()

        if not all([args["name"], args["id"]]):
            abort(400, message="参数错误")

        try:
            with db.auto_commit():
                rangeModel.GatekeeperRule.query.filter_by(id=args["id"]).update(args)
                return {"code": 200, "data": "修改成功"}

        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="系统错误")

    def delete(self, id):
        gatekeeper_rule_info = rangeModel.GatekeeperRule.query.filter_by(id=id).first()
        if not gatekeeper_rule_info:
            abort(400, message="参数错误")

        with db.auto_commit():
            db.session.delete(gatekeeper_rule_info)
            return {"code": 200, "data": "删除成功"}


class Taas(VmBase):
    def __init__(self, api=None, *args, **kwargs):
        super().__init__(api=api, *args, **kwargs)
        openstackAuthConfig = decrypt_pw(current_app.config["OPENSTACK"]["auth"])
        config = copy.copy(openstackAuthConfig)
        config["username"] = "admin"
        config["password"] = "adminadmin.123123"
        config["project_id"] = "ecc33e26d5f6463e8b071999aebd7f1f"
        self.admin_conn = openstack.connect(
            auth=config, auth_type=current_app.config["OPENSTACK"]["auth_type"]
        )

    def get_port(self, server):
        address = server.addresses
        exexternal_network = self.conn.network.get_network(
            current_app.config["OPENSTACK"]["external_id"]
        )
        subnet_id = exexternal_network.subnet_ids[0]
        subnet = self.conn.network.get_subnet(subnet_id)

        for net_name, net_info_list in address.items():  # noqa
            for addr_info in net_info_list:
                ditc = {"mac_address": addr_info["OS-EXT-IPS-MAC:mac_addr"]}
                ports = self.conn.network.ports(**ditc)
                for port in ports:
                    ip_address = port.fixed_ips[0]["ip_address"]
                    if ip_address not in IPy.IP(subnet.cidr):
                        return port.id

    def create_tap_service(self, port_id, range_uuid):
        try:
            tap_service = self.conn.create_tap_service(port_id)
            tap_service_id = tap_service["tap_service"]["id"]
            obj = taasModel.TapService(
                tap_service=tap_service_id, port_id=port_id, range_uuid=range_uuid
            )
            taasModel.db.session.add(obj)
            taasModel.db.session.commit()
        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="创建审计失败")
        return tap_service_id

    def create_flow(self, tap_service, source_port_list, range_uuid):
        try:
            for source_port in source_port_list:
                flow = self.conn.create_tap_flow(source_port, tap_service)["tap_flow"]
                obj = taasModel.TapFlow(
                    tap_service=tap_service,
                    flow_id=flow["id"],
                    source_port_id=source_port,
                    host='',
                    range_uuid=range_uuid,
                )
                taasModel.db.session.add(obj)
                taasModel.db.session.commit()
        except Exception as e:
            current_app.logger.exception(e)
            abort(500, message="创建失败")

    def delete_tap_service(self, range_uuid):
        tap_s = taasModel.TapService.query.filter_by(range_uuid=range_uuid).first()
        if tap_s:
            self.conn.delete_tap_service(tap_s.tap_service)
            flow_list = taasModel.TapFlow.query.filter_by(range_uuid=range_uuid).all()
            for flow in flow_list:
                self.conn.delete_flow(flow.flow_id)

    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument("range_uuid", help="靶场信息错误", required=True)
        args = parser.parse_args()

        tap_service = taasModel.TapService.query.filter_by(
            range_uuid=args["range_uuid"]
        ).all()
        if tap_service:
            abort(400, message="网络监控已开启")

        tap_serve_id = ""
        flow_list = []
        vm_list = rangeModel.RangeVm.query.filter_by(
            range_uuid=args["range_uuid"]
        ).all()

        for vm in vm_list:
            if vm.op_vm_uuid and vm.type == "NIDS":
                tap_serve_id = vm.op_vm_uuid
            elif vm.op_vm_uuid and vm.type != "HIDS":
                flow_list.append(vm.op_vm_uuid)
            elif not vm.op_vm_uuid:
                abort(400, message="虚拟机%s未开机" % vm.vm_name)
        for vm in vm_list:
            server = self.conn.compute.find_server(vm.op_vm_uuid)
            if server.status != "ACTIVE":
                abort(400, message="虚拟机%s未创建，请稍后重试" % vm.vm_name)

        tap_server = self.conn.compute.get_server(tap_serve_id)
        tap_port = self.get_port(tap_server)
        flow_port_list = []
        for flow in flow_list:
            flow_server = self.conn.compute.get_server(flow)
            port_id = self.get_port(flow_server)
            flow_port_list.append(port_id)
        if not flow_port_list:
            abort(400, message="没有可被监控的虚拟机")
        tap_service_id = self.create_tap_service(tap_port, args["range_uuid"])
        self.create_flow(tap_service_id, flow_port_list, args["range_uuid"])
        return {"code": 200, "data": "镜像端口设置成功"}


# 为虚拟机绑定赛题接口
class BindIssue(VmBase):
    def check_bind(self, range_manager, range_vm, question_ids, kwargs):

        question_list: List[CtfQuestion] = CtfQuestion.query.filter(
            CtfQuestion.id.in_(question_ids), CtfQuestion.answer != ""
        ).all()
        if len(question_list) != len(question_ids):
            abort(400, message="部分赛题无法定位")

        ctf_temp_lock = redis_store.get(f"TEMP_CTF_RANGE_LOCK::{kwargs['range_uuid']}")
        if ctf_temp_lock:
            abort(400, message=f"当前靶场已被ctf比赛{ctf_temp_lock.decode()}临时锁定，请稍后操作")

        # 如果当前靶场已被关联到某比赛，则校验赛题绑定关系是否允许被编辑
        if (
                range_manager.link_type == RangeLinkType.ctf
                and range_manager.link_obj.status != 2
        ):
            # 当前虚拟机已绑定赛题
            bind_issue_list = set(range_vm.bind_question)
            # 当前关联ctf比赛已添加赛题
            linked_issue_list = {
                val
                for item in range_manager.link_obj.linkQuestion
                for val in item.question
            }
            # 如果关联的比赛所引用的赛题中包含该虚拟机的赛题，但是这些赛题不在新建立的绑定关系中
            if not all(
                    [
                        bind_issue_list & linked_issue_list,  # 避免空集影响
                        set(question_list).issuperset(bind_issue_list & linked_issue_list),
                    ]
            ):
                abort(400, message="当前绑定的赛题与当前场景关联的ctf比赛中选择的赛题冲突，请检查后再操作")
        elif range_manager.link_type == RangeLinkType.ctf_red_blue:
            if range_manager.link_obj.status == CtfRedBlue.StatusChoices.in_progress:
                abort(
                    400,
                    message=f"该虚拟机所在场景已关联红蓝对抗赛: {range_manager.link_obj.name}，同时比赛已开启，禁止再对赛题进行操作",
                )

        # 校验唯一flag
        new_issue_answer = {
            item.answer for item in set(question_list) - set(range_vm.bind_question)
        }
        if new_issue_answer:
            link_flag_list = {
                val.answer
                for item in range_manager.range_model.vmList
                for val in item.bind_question
            }
            if new_issue_answer & link_flag_list:
                abort(400, message="新增赛题中部分赛题答案与该场景中其他赛题答案重复，请重新选择赛题或修改赛题答案")

        return question_list

    def post(self):
        parser = reqparse.RequestParser(bundle_errors=True)
        parser.add_argument("range_uuid", help="靶场ID不能为空", required=True)
        parser.add_argument("vm_uuid", help="虚拟机ID不能为空", required=True)
        parser.add_argument(
            "question_list", help="验证码id不能为空", type=list, location="json"
        )
        kwargs = parser.parse_args()
        range_uuid, vm_uuid, question_ids = (
            kwargs["range_uuid"],
            kwargs["vm_uuid"],
            kwargs["question_list"],
        )

        range_manager = RangeManager(range_uuid=range_uuid)
        range_model = range_manager.range_model
        if range_model.Range.is_system:
            abort(400, message="模板靶场不可更改！")

        range_vm: RangeVm = range_manager.get_vm_from_this(vm_uuid=vm_uuid)

        # 建立绑定关系
        try:
            if not question_ids:
                range_vm.bind_question = []
            else:
                question_list = self.check_bind(
                    range_manager, range_vm, question_ids, kwargs
                )
                range_vm.bind_question = question_list
            db.session.add(range_vm)
            db.session.commit()
        except HTTPException as e:  # 主动抛出自定义异常，如abort
            db.session.rollback()
            raise e
        except Exception as e:
            db.session.rollback()
            current_app.logger.error("保存虚拟机赛题失败", exc_info=e)
            abort(400, message="保存失败")
        else:
            return {"code": 200, "data": ""}


# 查看虚拟机已绑定赛题接口
class BindIssueList(VmBase):
    def get(self):
        params = request.args
        range_uuid = params.get("range_uuid", None)
        vm_uuid = params.get("vm_uuid", None)

        range_manager = RangeManager(range_uuid=range_uuid)
        range_model = range_manager.range_model
        if range_model.Range.is_system:
            abort(400, message="模板靶场不可更改！")

        vm: RangeVm = RangeVm.query.filter(
            RangeVm.vm_uuid == vm_uuid, RangeVm.range_uuid == range_uuid
        ).first()
        data = [
            {
                "id": item.id,
                "name": item.name,
            }
            for item in vm.bind_question
        ]
        data.sort(key=lambda x: x["id"])

        return {"code": 200, "data": data}
